#include <linux/module.h>
#include <linux/pci.h>
#include "drv_iepem.h"
#include "drv_pcie.h"
#include "drv_cdev.h"
#include "drv_ai.h"
#include "drv_xcan.h"

#if (HARD_VER == 0)
#define IEPEM_AI_DEV_NUM            16                  /* AI 设备数量 */
#define IEPEM_CANFD_DEV_NUM         1                   /* CANFD 设备数量 */
#elif (HARD_VER == 1)
#define IEPEM_AI_DEV_NUM            16                  /* AI 设备数量 */
#define IEPEM_CANFD_DEV_NUM         2                   /* CANFD 设备数量 */
#endif
#define IEPEM_AI_DRV_NAME           "pcie_ai"           /* AI 设备名称 */
#define IEPEM_CANFD_DRV_NAME        "pcie_canfd"        /* CANFD 设备名称 */

struct cdev_dev_t iepem_ai_cdev[IEPEM_AI_DEV_NUM];          /* AI 模块 AI 字符设备 */
struct ai_dev_t iepem_ai_dev[IEPEM_AI_DEV_NUM];             /* AI 模块 AI 设备 */
struct cdev_dev_t iepem_canfd_cdev[IEPEM_CANFD_DEV_NUM];    /* ECU 模块 CANFD 字符设备 */
struct xcan_dev_t iepem_canfd_dev[IEPEM_CANFD_DEV_NUM];     /* ECU 模块 CANFD 设备 */

/* PCIe AI 模块 */
struct pcie_iepem_t {
    struct device *dev;                 /* Linux 设备指针 */
    struct pcie_dev_t pcie_dev;         /* PCIe 设备 */
    struct ai_ctrl_t ai_ctrl;           /* AI 控制器 */
    struct xcan_ctrl_t xcanfd_ctrl;     /* CANFD 控制器 */
};

static int pcie_iepem_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
    int ret = 0;
    struct pcie_iepem_t *pmcdev = NULL;

    dev_info(&pdev->dev, "Probing %04x:%04x", pdev->vendor, pdev->device);
    /* 检查设备是否已被占用 */
    if (pci_get_drvdata(pdev)) {
        dev_warn(&pdev->dev, "device already claimed");
        return -EBUSY;
    }

    /* 分配设备结构内存 */
    pmcdev = devm_kzalloc(&pdev->dev, sizeof(*pmcdev), GFP_KERNEL);
    if (!pmcdev)
        return -ENOMEM;

    pmcdev->dev = &pdev->dev;
    pci_set_drvdata(pdev, pmcdev);

    /* 初始化 PCIe 设备 */
    ret = init_pcie_dev(pdev, &pmcdev->pcie_dev);
    if (ret) {
        dev_err(pmcdev->dev, "Init PCIe device failed");
        goto err_cleanup;
    }

    /* 初始化 AI 控制器 */
    ret = init_ai_ctrl(&pmcdev->pcie_dev, iepem_ai_cdev, &pmcdev->ai_ctrl, 
                       iepem_ai_dev, IEPEM_AI_DRV_NAME, IEPEM_AI_DEV_NUM, AI_TYPE_AI);
    if (ret < 0) {
        dev_err(pmcdev->dev, "Init AI controller failed");
        goto err_cleanup;
    }
    dev_info(pmcdev->dev, "Init AI controller success");

    /* 初始化 CAN FD 控制器 */
    ret = init_xcan_ctrl(&pmcdev->pcie_dev, iepem_canfd_cdev, &pmcdev->xcanfd_ctrl, 
                         iepem_canfd_dev, IEPEM_CANFD_DRV_NAME, IEPEM_CANFD_DEV_NUM, &xcan_canfd2_data);
    if (ret < 0) {
        dev_err(pmcdev->dev, "Init CANFD device failed");
        goto err_cleanup_ai;
    }
    dev_info(pmcdev->dev, "Init CANFD device success");

    dev_info(&pdev->dev, "Probe PCIe %s device success", IEPEM_DRV_NAME);

    return 0;

err_cleanup_ai:
    /* 注销 AI 控制器 */
    exit_ai_ctrl(&pmcdev->ai_ctrl, IEPEM_AI_DEV_NUM);
err_cleanup:
    pci_set_drvdata(pdev, NULL);

    return ret;
}

static void pcie_iepem_remove(struct pci_dev *pdev)
{
    struct pcie_iepem_t *pmcdev = pci_get_drvdata(pdev);

    if (!pmcdev) {
        dev_warn(&pdev->dev, "No device data found");
        return;
    }
    dev_info(&pdev->dev, "Removing PCIe %s device", IEPEM_DRV_NAME);

    /* 清理 CAN FD 控制器 */
    exit_xcan_ctrl(&pmcdev->xcanfd_ctrl, IEPEM_CANFD_DEV_NUM);
    dev_info(pmcdev->dev, "Exit %s controller success", pmcdev->xcanfd_ctrl.name);

    /* 注销 AI 控制器 */
    exit_ai_ctrl(&pmcdev->ai_ctrl, IEPEM_AI_DEV_NUM);
    dev_info(pmcdev->dev, "Exit %s controller success", pmcdev->ai_ctrl.name);

    /* 注销 PCIe 设备 */
    exit_pcie_dev(pdev, &pmcdev->pcie_dev);
    dev_info(&pdev->dev, "Exit %s device success", pmcdev->pcie_dev.name);

    pci_set_drvdata(pdev, NULL);
    dev_info(&pdev->dev, "Remove PCIe %s device success", IEPEM_DRV_NAME);
}

/* PCI 设备 ID 表 */
static const struct pci_device_id pcie_iepem_ids[] = {
    { PCI_DEVICE(0x594D, 0x000A) },     /* iepe module */
    { 0, }
};
MODULE_DEVICE_TABLE(pci, pcie_iepem_ids);

/* PCI AI 驱动结构 */
struct pci_driver pcie_iepem_driver = {
    .name = IEPEM_DRV_NAME,
    .id_table = pcie_iepem_ids,
    .probe = pcie_iepem_probe,
    .remove = pcie_iepem_remove,
};
